package com.learn.spring_mvc.service;

import com.learn.spring_mvc.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    
    /*
     * @Transactional：事物控制注解，配合@EnableTransactionManagement注解使用，@EnableTransactionManagement通常添加在事务管理器托管的配置类上
     *      若写在类上则类内所有方法均开启该事务，若写在方法上则只有当前方法开启事务
     *  transactionManager：事务管理器
     *  timeout：超时时间，默认无超时时间-1
     *  readOnly：只读，默认false
     *  rollbackFor：遇到某些异常就回滚
     *  isolation：隔离级别，解决并发问题
     *      · DEFAULT：默认隔离级别，取决于当前数据库的隔离级别。MySQL默认采用REPEATABLE_READ，Oracle默认采用READ_COMMITTED
     *      · READ_UNCOMMITTED：读未提交。不能解决脏读、不可重复读、幻读
     *      · READ_COMMITTED：读已提交。不能解决不可重复读、幻读
     *      · REPEATABLE_READ：可重复读。不能解决幻读
     *      · SERIALIZABLE：可串行化。隔离级别最高，但性能最低
     *  propagation：传播机制，解决事务嵌套问题。例如业务A调用业务B，此时业务A和业务B都存在自己的事务，那事务用谁的？
     *      · REQUIRED(默认)：A调用B，B需要事务，若A有事务则B加入A的事务中，若A无事务则B创建自己的事务
     *      · REQUIRES_NEW：A调用B，B需要新事务，若A有事务则A事务挂起且B创建自己的新事务，若A无事务则B创建自己的事务，AB事务互不干扰
     *      · SUPPORTS：A调用B，B无所谓有无事务，若A有事务则B加入A的事务中，若A无事务则B以无事务方式运行
     *      · NOT_SUPPORTED：A调用B，B不需要事务，若A有事务则A事务挂起且B以无事务方式运行，若A无事务则B正好以无事务方式运行
     *      · NEVER：A调用B，B不需要事务，若A有事务则抛出异常，若A无事务则B正好以无事务方式运行
     *      · MANDATORY：A调用B，B加入A事务，若A无事务则抛出异常
     *      · NESTED：A调用B，B需要新事务，若A有事务则A事务作为嵌套事务且B创建自己的新事务，若A无事务则B创建自己的事务
     */
    @Override
    @Transactional(
            transactionManager = "jdbcTransactionManager",
            isolation = Isolation.REPEATABLE_READ,
            propagation = Propagation.REQUIRED
    )
    public void transferMoney(Integer inAccountId, Integer outAccountId, Integer money) {
        accountDao.decrMoney(outAccountId, money);
        
        // 模拟转账失败
        // int i = 1 / 0;
        
        accountDao.incrMoney(inAccountId, money);
    }
}
